[xend / libxen] Add support for labeling of virtual network interfaces.
authorkfraser@localhost.localdomain <kfraser@localhost.localdomain>
Thu, 19 Jul 2007 15:59:48 +0000 (16:59 +0100)
committerkfraser@localhost.localdomain <kfraser@localhost.localdomain>
Thu, 19 Jul 2007 15:59:48 +0000 (16:59 +0100)
This patch adds labeling of virtual network interfaces to xend and
makes this manageable through the Xen-API.  It's a feature that is
only usable if ACM is enabled in Xen and xend is used through the
xen-api. A labeled virtual network interface will be plugged into a
bridge where other domains with the same-labeled network interface are
connected to, so that only same-colored domains can communicate with
each other. The bridge should be connected to the outside world using
VLAN for isolation, extending the isolation beyond the local machine.
If a virtual machine is labeled with a VM label that only has one
Simple Type Enforcement Type then it is not necessary to label the
virtual network interface, but the color of the network interface is
determined from the VM's label. If, however, a virtual machine is
labeled with a VM label that has multiple Simple Type Enforcement
Types, then the explicit labeling of each virtual network interface is
required. To specify the label of a network interface, the vif line in
the VM's configuration file has been extended with parameters similar
use for specifying the label of the VM:

vif = ['policy=<policy name>,label=<resource label>']

This labels the VIF of the virtual machine for usage under the policy
'policy name' and labels it with the label 'resource label'.

Signed-off-by: Stefan Berger <stefanb@us.ibm.com>
15 files changed:
tools/libxen/include/xen/api/xen_vif.h
tools/libxen/src/xen_vif.c
tools/python/xen/util/security.py
tools/python/xen/xend/XendAPI.py
tools/python/xen/xend/XendConfig.py
tools/python/xen/xend/XendDomain.py
tools/python/xen/xend/XendDomainInfo.py
tools/python/xen/xend/XendXSPolicyAdmin.py
tools/python/xen/xend/server/netif.py
tools/python/xen/xm/addlabel.py
tools/python/xen/xm/create.dtd
tools/python/xen/xm/create.py
tools/python/xen/xm/getlabel.py
tools/python/xen/xm/rmlabel.py
tools/python/xen/xm/xenapi_create.py

index 26608f0af9b29ee65fbe0c2647d61a48d86b2864..3fb8d7128e6626a039e602f65493c497fafdf162 100644 (file)
@@ -362,4 +362,18 @@ extern bool
 xen_vif_get_all(xen_session *session, struct xen_vif_set **result);
 
 
+/**
+ * Set the security label of a VIF.
+ */
+extern bool
+xen_vif_set_security_label(xen_session *session, int64_t *result, xen_vif vif,
+                           char *label, char *oldlabel);
+
+
+/**
+ * Get the security label of a VIF.
+ */
+extern bool
+xen_vif_get_security_label(xen_session *session, char **result, xen_vif vif);
+
 #endif
index bc9dd0dd2fe64bfe44d34868cc02157f44436c97..ac6147ff4f85bdb154a4de1980dc1a0632a1b373 100644 (file)
@@ -575,3 +575,42 @@ xen_vif_get_uuid(xen_session *session, char **result, xen_vif vif)
     XEN_CALL_("VIF.get_uuid");
     return session->ok;
 }
+
+
+bool
+xen_vif_set_security_label(xen_session *session, int64_t *result, xen_vif vif,
+                           char *label, char *oldlabel)
+{
+    abstract_value param_values[] =
+        {
+            { .type = &abstract_type_string,
+              .u.string_val = vif },
+            { .type = &abstract_type_string,
+              .u.string_val = label },
+            { .type = &abstract_type_string,
+              .u.string_val = oldlabel },
+        };
+
+    abstract_type result_type = abstract_type_int;
+
+    *result = 0;
+    XEN_CALL_("VIF.set_security_label");
+    return session->ok;
+}
+
+
+bool
+xen_vif_get_security_label(xen_session *session, char **result, xen_vif vif)
+{
+    abstract_value param_values[] =
+        {
+            { .type = &abstract_type_string,
+              .u.string_val = vif },
+        };
+
+    abstract_type result_type = abstract_type_string;
+
+    *result = NULL;
+    XEN_CALL_("VIF.get_security_label");
+    return session->ok;
+}
index 608ed463f2181c20c0dd509c9e1dfac985abc2a6..5183ed2c989e81a5bd036ff15a807af10afaac32 100644 (file)
@@ -831,7 +831,7 @@ def get_domain_resources(dominfo):
         Entries are strored in the following formats:
           tap:qcow:/path/xyz.qcow
     """
-    resources = { 'vbd' : [], 'tap' : []}
+    resources = { 'vbd' : [], 'tap' : [], 'vif' : []}
     devs = dominfo.info['devices']
     uuids = devs.keys()
     for uuid in uuids:
@@ -839,6 +839,15 @@ def get_domain_resources(dominfo):
         typ = dev[0]
         if typ in [ 'vbd', 'tap' ]:
             resources[typ].append(dev[1]['uname'])
+        if typ in [ 'vif' ]:
+            sec_lab = dev[1].get('security_label')
+            if sec_lab:
+                resources[typ].append(sec_lab)
+            else:
+                resources[typ].append("%s:%s:%s" %
+                                      (xsconstants.ACM_POLICY_ID,
+                                       active_policy,
+                                       "unlabeled"))
 
     return resources
 
@@ -874,23 +883,36 @@ def __resources_compatible_with_vmlabel(xspol, dominfo, vmlabel,
         dictionary of the resource name to resource label mappings
         under which the evaluation should be done.
     """
+    def collect_labels(reslabels, s_label, polname):
+        if len(s_label) != 3 or polname != s_label[1]:
+            return False
+        label = s_label[2]
+        if not label in reslabels:
+            reslabels.append(label)
+        return True
+
     resources = get_domain_resources(dominfo)
     reslabels = []  # all resource labels
+
     polname = xspol.get_name()
-    for key in resources.keys():
-        for res in resources[key]:
-            try:
-                tmp = access_control[res]
-                if len(tmp) != 3:
+    for key, value in resources.items():
+        if key in [ 'vbd', 'tap' ]:
+            for res in resources[key]:
+                try:
+                    label = access_control[res]
+                    if not collect_labels(reslabels, label, polname):
+                        return False
+                except:
                     return False
-
-                if polname != tmp[1]:
+        elif key in [ 'vif' ]:
+            for xapi_label in value:
+                label = xapi_label.split(":")
+                if not collect_labels(reslabels, label, polname):
                     return False
-                label = tmp[2]
-                if not label in reslabels:
-                    reslabels.append(label)
-            except:
-                return False
+        else:
+            log.error("Unhandled device type: %s" % key)
+            return False
+
     # Check that all resource labes have a common STE type with the
     # vmlabel
     rc = xspol.policy_check_vmlabel_against_reslabels(vmlabel, reslabels)
index 3d890e7c4441c5bb5423d0e8e896d8eddd1fe849..2192d334b5e79f3305d1bdd5eb4cf9e9ecfa71da 100644 (file)
@@ -2084,6 +2084,25 @@ class XendAPI(object):
     def VIF_get_security_label(self, session, vif_ref):
         return self._VIF_get(vif_ref, 'security_label')
 
+    def _VIF_set(self, ref, prop, val, old_val):
+        return XendDomain.instance().set_dev_property_by_uuid(
+                       'vif', ref, prop, val, old_val)
+
+    def VIF_set_security_label(self, session, vif_ref, sec_lab, old_lab):
+        xendom = XendDomain.instance()
+        dom = xendom.get_vm_with_dev_uuid('vif', vif_ref)
+        if not dom:
+            return xen_api_error(['HANDLE_INVALID', 'VIF', vif_ref])
+
+        if dom._stateGet() == XEN_API_VM_POWER_STATE_RUNNING:
+            raise SecurityError(-xsconstants.XSERR_RESOURCE_IN_USE)
+
+        rc = self._VIF_set(vif_ref, 'security_label', sec_lab, old_lab)
+        if rc == False:
+            raise SecurityError(-xsconstants.XSERR_BAD_LABEL)
+        return xen_api_success(xsconstants.XSERR_SUCCESS)
+
+
     # Xen API: Class VIF_metrics
     # ----------------------------------------------------------------
 
index 4dac85061b8568b9e9817f343859e735f32b4c45..5bc40a5dfb35ad5718014b71b8b8729a269175ca 100644 (file)
@@ -1085,6 +1085,12 @@ class XendConfig(dict):
 
             self.device_duplicate_check(dev_type, dev_info, target)
 
+            if dev_type == 'vif':
+                if dev_info.get('policy') and dev_info.get('label'):
+                    dev_info['security_label'] = "%s:%s:%s" % \
+                        (xsconstants.ACM_POLICY_ID,
+                         dev_info['policy'],dev_info['label'])
+
             # create uuid if it doesn't exist
             dev_uuid = dev_info.get('uuid', None)
             if not dev_uuid:
@@ -1159,6 +1165,10 @@ class XendConfig(dict):
                     network = XendAPIStore.get(
                         cfg_xenapi.get('network'), 'network')
                     dev_info['bridge'] = network.get_name_label()
+
+                if cfg_xenapi.get('security_label'):
+                    dev_info['security_label'] = \
+                         cfg_xenapi.get('security_label')
                 
                 dev_uuid = cfg_xenapi.get('uuid', None)
                 if not dev_uuid:
index c951dc6af8ab00d9d806088ecac74ed5466959a2..3d483655711612b0f90bf4ddb100ac7948075a9d 100644 (file)
@@ -688,6 +688,29 @@ class XendDomain:
         
         return value
 
+    def set_dev_property_by_uuid(self, klass, dev_uuid, field, value,
+                                 old_val = None):
+        rc = True
+        self.domains_lock.acquire()
+
+        try:
+            try:
+                dom = self.get_vm_with_dev_uuid(klass, dev_uuid)
+                if dom:
+                    o_val = dom.get_dev_property(klass, dev_uuid, field)
+                    log.info("o_val=%s, old_val=%s" % (o_val, old_val))
+                    if old_val and old_val != o_val:
+                        return False
+
+                    dom.set_dev_property(klass, dev_uuid, field, value)
+                    self.managed_config_save(dom)
+            except ValueError, e:
+                pass
+        finally:
+            self.domains_lock.release()
+
+        return rc
+
     def is_valid_vm(self, vm_ref):
         return (self.get_vm_by_uuid(vm_ref) != None)
 
index a69211d32bd3d9e43ed62f410b578a07892dc7d8..94a02049a6f2a141687a4e805b846e5e82deae92 100644 (file)
@@ -2420,6 +2420,8 @@ class XendDomainInfo:
                 config['io_read_kbs'] = 0.0
                 config['io_write_kbs'] = 0.0                
 
+            config['security_label'] = config.get('security_label', '')
+
         if dev_class == 'vbd':
 
             if self._stateGet() not in (XEN_API_VM_POWER_STATE_HALTED,):
index 727cae664aabe28e087646389699138b225fe961..b3d7c2a9f18b49fe67caaf3cc18576d159b7531c 100644 (file)
@@ -312,6 +312,18 @@ class XSPolicyAdmin:
             vmlabel = pol.policy_get_domain_label_by_ssidref_formatted(ssidref)
         return vmlabel
 
+    def get_stes_of_vmlabel(self, vmlabel_xapi):
+        """ Get the list of STEs given a VM label in XenAPI format """
+        stes = []
+        loadedpol = self.get_loaded_policy()
+        if loadedpol:
+            tmp = vmlabel_xapi.split(":")
+            if len(tmp) != 3:
+                return []
+            stes = loadedpol.policy_get_stes_of_vmlabel(tmp[2])
+        return stes
+
+
 poladmin = None
 
 def XSPolicyAdminInstance(maxpolicies=1):
index 130668428cd9cdb8dac89fcd800b499680bc3eeb..3d4b598b91d6f875e527b7a1e63a6143785af98e 100644 (file)
@@ -26,6 +26,11 @@ import re
 
 from xen.xend import XendOptions
 from xen.xend.server.DevController import DevController
+from xen.xend.XendError import VmError
+from xen.util import security
+from xen.xend.XendXSPolicyAdmin import XSPolicyAdminInstance
+
+from xen.xend.XendLogging import log
 
 xoptions = XendOptions.instance()
 
@@ -108,6 +113,7 @@ class NetifController(DevController):
         ipaddr  = config.get('ip')
         model   = config.get('model')
         accel   = config.get('accel')
+        sec_lab = config.get('security_label')
 
         if not typ:
             typ = xoptions.netback_type
@@ -134,6 +140,8 @@ class NetifController(DevController):
             back['model'] = model
         if accel:
             back['accel'] = accel
+        if sec_lab:
+            back['security_label'] = sec_lab
 
         config_path = "device/%s/%d/" % (self.deviceClass, devid)
         for x in back:
@@ -149,9 +157,34 @@ class NetifController(DevController):
             front = { 'handle' : "%i" % devid,
                       'mac'    : mac }
 
+        if security.on():
+            self.do_access_control(config)
+
         return (devid, back, front)
 
 
+    def do_access_control(self, config):
+        """ do access control checking. Throws a VMError if access is denied """
+        domain_label = self.vm.get_security_label()
+        stes = XSPolicyAdminInstance().get_stes_of_vmlabel(domain_label)
+        res_label = config.get('security_label')
+        if len(stes) > 1 or res_label:
+            if not res_label:
+                raise VmError("'VIF' must be labeled")
+            (label, ssidref, policy) = \
+                              security.security_label_to_details(res_label)
+            if domain_label:
+                rc = security.res_security_check_xapi(label, ssidref,
+                                                      policy,
+                                                      domain_label)
+                if rc == 0:
+                    raise VmError("VM's access to network device denied. "
+                                  "Check labeling")
+            else:
+                raise VmError("VM must have a security label to access "
+                              "network device")
+
+
     def getDeviceConfiguration(self, devid):
         """@see DevController.configuration"""
 
@@ -160,10 +193,12 @@ class NetifController(DevController):
         config_path = "device/%s/%d/" % (self.deviceClass, devid)
         devinfo = ()
         for x in ( 'script', 'ip', 'bridge', 'mac',
-                   'type', 'vifname', 'rate', 'uuid', 'model', 'accel'):
+                   'type', 'vifname', 'rate', 'uuid', 'model', 'accel',
+                   'security_label'):
             y = self.vm._readVm(config_path + x)
             devinfo += (y,)
-        (script, ip, bridge, mac, typ, vifname, rate, uuid, model, accel) = devinfo
+        (script, ip, bridge, mac, typ, vifname, rate, uuid,
+         model, accel, security_label) = devinfo
 
         if script:
             result['script'] = script
@@ -185,5 +220,7 @@ class NetifController(DevController):
             result['model'] = model
         if accel:
             result['accel'] = accel
-            
+        if security_label:
+            result['security_label'] = security_label
+
         return result
index 9e9364128444c178b2f67f73d834d24e37ca8689..bb27d30331f03f582322144cc2a3c5935675c199 100644 (file)
@@ -34,6 +34,7 @@ def help():
     Format: xm addlabel <label> dom <configfile> [<policy>]
             xm addlabel <label> mgt <domain name> [<policy type>:<policy>]
             xm addlabel <label> res <resource> [[<policy type>:]<policy>]
+            xm addlabel <label> vif-<idx> <domain name> [<policy type>:<policy>]
     
     This program adds an acm_label entry into the 'configfile'
     for a domain or allows to label a xend-managed domain.
@@ -162,6 +163,32 @@ def add_domain_label_xapi(label, domainname, policyref, policy_type):
             print "Set the label of dormant domain '%s' to '%s'." % \
                   (domainname,label)
 
+def add_vif_label(label, vmname, idx, policyref, policy_type):
+    if xm_main.serverType != xm_main.SERVER_XEN_API:
+        raise OptionError('Need to be configure for using xen-api.')
+    vm_refs = server.xenapi.VM.get_by_name_label(vmname)
+    if len(vm_refs) == 0:
+        raise OptionError('A VM with the name %s does not exist.' %
+                          vmname)
+    vif_refs = server.xenapi.VM.get_VIFs(vm_refs[0])
+    if len(vif_refs) <= idx:
+        raise OptionError("Bad VIF index.")
+    vif_ref = server.xenapi.VIF.get_by_uuid(vif_refs[idx])
+    if not vif_ref:
+        print "Internal error: VIF does not exist."
+    sec_lab = "%s:%s:%s" % (policy_type, policyref, label)
+    try:
+        old_lab = server.xenapi.VIF.get_security_label(vif_ref)
+        rc = server.xenapi.VIF.set_security_label(vif_ref,
+                                                  sec_lab, old_lab)
+        if int(rc) != 0:
+            print "Could not label the VIF."
+        else:
+            print "Successfully labeled the VIF."
+    except Exception, e:
+        print "Could not label the VIF: %s" % str(e)
+
+
 def main(argv):
     policyref = None
     policy_type = ""
@@ -209,6 +236,20 @@ def main(argv):
             else:
                 raise OptionError("Policy name in wrong format.")
         add_resource_label(label, resource, policyref, policy_type)
+    elif argv[2].lower().startswith("vif-"):
+        try:
+            idx = int(argv[2][4:])
+            if idx < 0:
+                raise
+        except:
+            raise OptionError("Bad VIF device index.")
+        vmname = argv[3]
+        if policy_type == "":
+            tmp = policyref.split(":")
+            if len(tmp) != 2:
+                raise OptionError("Policy name in wrong format.")
+            policy_type, policyref = tmp
+        add_vif_label(label, vmname, idx, policyref, policy_type)
     else:
         raise OptionError('Need to specify either "dom", "mgt" or "res" as '
                           'object to add label to.')
index f9c7fe89573f84b1a2fb28e02d9c9194a33465b2..efb3cbfd3e929de9160d9bbc533aa72cb376580a 100644 (file)
@@ -74,7 +74,8 @@
                  mtu             CDATA       #REQUIRED
                  device          CDATA       #REQUIRED
                  qos_algorithm_type CDATA    #REQUIRED
-                 network         CDATA       #IMPLIED> 
+                 network         CDATA       #IMPLIED
+                 security_label  CDATA       #IMPLIED>
 
 <!ELEMENT vtpm   (name*)>
 <!ATTLIST vtpm   backend         CDATA #REQUIRED>
index bca85abbde76b3d67b724050e5edf38c8c38fcbd..f4d056608bb044ddd5a39d5e41276c01a2c2d33a 100644 (file)
@@ -704,7 +704,8 @@ def configure_vifs(config_devs, vals):
 
         def f(k):
             if k not in ['backend', 'bridge', 'ip', 'mac', 'script', 'type',
-                         'vifname', 'rate', 'model', 'accel']:
+                         'vifname', 'rate', 'model', 'accel',
+                         'policy', 'label']:
                 err('Invalid vif option: ' + k)
 
             config_vif.append([k, d[k]])
index 5954ca923bf755db679875317d2000678e5ac5a4..cf7033d7d48a130d3644c522b92f8c4e2e766d9e 100644 (file)
@@ -31,6 +31,7 @@ def help():
     Usage: xm getlabel dom <configfile>
            xm getlabel mgt <domain name>
            xm getlabel res <resource>
+           xm getlabel vif-<idx> <vmname>
            
     This program shows the label for a domain, resource or virtual network
     interface of a Xend-managed domain."""
@@ -103,6 +104,22 @@ def get_domain_label(configfile):
     data = data.rstrip("\']")
     print "policytype=%s," % xsconstants.ACM_POLICY_ID + data
 
+def get_vif_label(vmname, idx):
+    if xm_main.serverType != xm_main.SERVER_XEN_API:
+        raise OptionError('xm needs to be configure to use the xen-api.')
+    vm_refs = server.xenapi.VM.get_by_name_label(vmname)
+    if len(vm_refs) == 0:
+        raise OptionError('A VM with the name %s does not exist.' %
+                          vmname)
+    vif_refs = server.xenapi.VM.get_VIFs(vm_refs[0])
+    if len(vif_refs) <= idx:
+        raise OptionError("Bad VIF index.")
+    vif_ref = server.xenapi.VIF.get_by_uuid(vif_refs[idx])
+    if not vif_ref:
+        print "No VIF with this UUID."
+    sec_lab = server.xenapi.VIF.get_security_label(vif_ref)
+    print "%s" % sec_lab
+
 def get_domain_label_xapi(domainname):
     if xm_main.serverType != xm_main.SERVER_XEN_API:
         raise OptionError('xm needs to be configure to use the xen-api.')
@@ -128,6 +145,15 @@ def main(argv):
     elif argv[1].lower() == "res":
         resource = argv[2]
         get_resource_label(resource)
+    elif argv[1].lower().startswith("vif-"):
+        try:
+            idx = int(argv[1][4:])
+            if idx < 0:
+                raise
+        except:
+            raise OptionError("Bad VIF device index.")
+        vmname = argv[2]
+        get_vif_label(vmname, idx)
     else:
         raise OptionError('First subcommand argument must be "dom"'
                           ', "mgt" or "res"')
index 2eb9c4b6d4c72e24b2aa07ceacff7717c9b9429b..c3c488fc5b8037d2be48851a9e196ca1575148bc 100644 (file)
@@ -30,6 +30,7 @@ def help():
     Example: xm rmlabel dom <configfile>
              xm rmlabel res <resource>
              xm rmlabel mgt <domain name>
+             xm rmlabel vif-<idx> <domain name>
 
     This program removes an acm_label entry from the 'configfile'
     for a domain, from a Xend-managed domain, from the global resource label
@@ -129,24 +130,55 @@ def rm_domain_label_xapi(domainname):
     except Exception, e:
         print('Could not remove label from domain: %s' % e)
 
+def rm_vif_label(vmname, idx):
+    if xm_main.serverType != xm_main.SERVER_XEN_API:
+        raise OptionError('Need to be configure for using xen-api.')
+    vm_refs = server.xenapi.VM.get_by_name_label(vmname)
+    if len(vm_refs) == 0:
+        raise OptionError('A VM with the name %s does not exist.' %
+                          vmname)
+    vif_refs = server.xenapi.VM.get_VIFs(vm_refs[0])
+    if len(vif_refs) <= idx:
+        raise OptionError("Bad VIF index.")
+    vif_ref = server.xenapi.VIF.get_by_uuid(vif_refs[idx])
+    if not vif_ref:
+        print "A VIF with this UUID does not exist."
+    try:
+        old_lab = server.xenapi.VIF.get_security_label(vif_ref)
+        rc = server.xenapi.VIF.set_security_label(vif_ref, "", old_lab)
+        if int(rc) != 0:
+            print "Could not remove the label from the VIF."
+        else:
+            print "Successfully removed the label from the VIF."
+    except Exception, e:
+        print "Could not remove the label the VIF: %s" % str(e)
+
 
 def main (argv):
 
     if len(argv) != 3:
         raise OptionError('Requires 2 arguments')
     
-    if argv[1].lower() not in ('dom', 'mgt', 'res'):
-        raise OptionError('Unrecognised type argument: %s' % argv[1])
-
     if argv[1].lower() == "dom":
         configfile = argv[2]
         rm_domain_label(configfile)
     elif argv[1].lower() == "mgt":
         domain = argv[2]
         rm_domain_label_xapi(domain)
+    elif argv[1].lower().startswith("vif-"):
+        try:
+            idx = int(argv[1][4:])
+            if idx < 0:
+                raise
+        except:
+            raise OptionError("Bad VIF device index.")
+        vmname = argv[2]
+        rm_vif_label(vmname, idx)
     elif argv[1].lower() == "res":
         resource = argv[2]
         rm_resource_label(resource)
+    else:
+        raise OptionError('Unrecognised type argument: %s' % argv[1])
 
 if __name__ == '__main__':
     try:
index 0a2d74a20985933af8b2c160c411970a47d5b204..70c91140b7d8c00f61d3769c886b849a7a7f3679 100644 (file)
@@ -440,7 +440,9 @@ class xenapi_create:
                 vif.attributes["qos_algorithm_type"].value,
             "qos_algorithm_params":
                 get_child_nodes_as_dict(vif,
-                    "qos_algorithm_param", "key", "value")
+                    "qos_algorithm_param", "key", "value"),
+            "security_label":
+                vif.attributes["security_label"].value
         }
 
         return server.xenapi.VIF.create(vif_record)
@@ -748,6 +750,15 @@ class sxp2xml:
         vif.attributes["device"] = dev
         vif.attributes["qos_algorithm_type"] = ""
 
+        policy = get_child_by_name(vif_sxp, "policy")
+        label = get_child_by_name(vif_sxp, "label")
+
+        if label and policy:
+            vif.attributes["security_label"] \
+                 = "%s:%s:%s" % (xsconstants.ACM_POLICY_ID, policy, label)
+        else:
+            vif.attributes["security_label"] = ""
+
         if get_child_by_name(vif_sxp, "bridge") is not None:
             vif.attributes["network"] \
                 = get_child_by_name(vif_sxp, "bridge")